
# This code is hosted on http://code.google.com/p/lenthorp/
# Freely available for use in applications, but should NOT be modified
# Email all comments to lenthorpresearch@gmail.com

import numpy as np
from math import log

tradingDaysPerYear = 252

# Assumes daily positions
def sharpeRatio(positions, riskFreeRate=0.0):

    returnVals = {}
    
    nberObs = len(positions)
    returns = np.zeros(nberObs-1)
    
    # calculate the average returns - only cares about the final position
    for posIdx in range(nberObs-1):
        positionsDates = positions[posIdx][0]
        positionsValues = positions[posIdx][1]
        tempReturn = positionsValues[-1] / positionsValues[0] # arithmetic returns
        investmentYears = float((positionsDates[-1] - positionsDates[0]).days) / 365.25
        # turn this into an annualised rate of return (continuous compounding)
        returns[posIdx] = log(tempReturn) / investmentYears
    excessReturn = returns.mean() - riskFreeRate

    # return the ratio and confidence
    returnVals['ratio'] = excessReturn / returns.std()
    ##returnVals['confidence'] = 

    return returnVals


def treynorRatio():
    pass

def sortinoRatio(positions, riskFreeRate=0.0):
    returnVals = {}
    
    nberObs = len(positions)
    returns = np.zeros(nberObs-1)
    
    # calculate the average returns - only cares about the final position
    for posIdx in range(nberObs-1):
        positionsDates = positions[posIdx][0]
        positionsValues = positions[posIdx][1]
        tempReturn = positionsValues[-1] / positionsValues[0] # arithmetic returns
        investmentYears = float((positionsDates[-1] - positionsDates[0]).days) / 365.25
        # turn this into an annualised rate of return (continuous compounding)
        returns[posIdx] = log(tempReturn) / investmentYears
    excessReturn = returns.mean() - riskFreeRate

    # return the ratio and confidence
    negReturnsList = [returns[idx] for idx in range(nberObs-1) if returns[idx] < 0.0]
    negReturns = np.zeros(len(negReturnsList))
    for idx in range(len(negReturnsList)):
        negReturns[idx] = negReturnsList[idx]
    if len(negReturnsList) < 2:
        returnVals['ratio'] = None
    else:
        returnVals['ratio'] = excessReturn / negReturns.std()
    ##returnVals['confidence'] = 

    return returnVals


# Calculates the largest drawdown and longest drawdown
def drawdown(positions):

    returnVals = {}
    largeDD = 0.0
    longDD = 0.0
    averageLarge = 0.0
    averageLong = 0.0

    nberObs = len(positions)
    ddSizes = np.zeros(nberObs-1)
    ddLengths = np.zeros(nberObs-1)
    
    for posIdx in range(nberObs-1):
        largeDD = 0
        longDD = 0
        position = positions[posIdx]
        dates = position[0]
        prices = position[1]
        peak = prices[0]
        ddStart = -1
        for datIdx in range(1,len(dates)):
            if prices[datIdx] < peak:
                if ddStart == -1: # drawdown started
                    ddStart = datIdx
                    nadir = prices[datIdx]
                else:
                    nadir = min(nadir, prices[datIdx])
            else:
                if ddStart != -1: # drawdown finished
                    largeDD = max(largeDD, (peak - nadir)/peak)
                    longDD = max(longDD, float((dates[datIdx] - dates[ddStart]).days) / 365.5)
                    ddStart = -1
                peak = max(peak, prices[datIdx])
        # make sure we capture the one at the end
        largeDD = max(largeDD, (peak - nadir)/peak if ddStart != -1 else 0)
        longDD = max(longDD, float((dates[datIdx] - dates[ddStart]).days) / 365.5 if ddStart != -1 else 0)
        ddSizes[posIdx] = largeDD
        ddLengths[posIdx] = longDD

    returnVals['largest'] = ddSizes.max()
    returnVals['longest'] = ddLengths.max()
    returnVals['averageLength'] = ddLengths.mean()
    returnVals['averageSize'] = ddSizes.mean()

    return returnVals